package com.zmx.core.client.auth;

import com.zmx.core.dto.ChannelInfo;
import com.zmx.core.utils.AESUtil;
import com.zmx.core.utils.HostUtil;
import com.zmx.core.utils.JacksonUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 客户端注册及心跳监测handler
 * @ClassName: ClientHandler
 * @Author zhaomxr
 * @Date 2021-12-14 15:23
 */
@Slf4j
public class ClientHandler extends SimpleChannelInboundHandler<ChannelInfo> {

    private String ip;
    private int port;
    private String username;
    private String password;
    private String secret;
    private String authUrl;
    private int authRetryTime;
    private int authHeartRetryTime;

    /**
     * 设置为守护线程，不影响服务正常执行
     */
    private ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("clientHandler").daemon(true).build());
    private ScheduledFuture<?> scheduledFuture;

    private static final String SUCCESS = "OK";

    public ClientHandler() {
    }

    public ClientHandler(int port, String username, String password, String secret, String authUrl, int authRetryTime, int authHeartRetryTime) {
        this.port = port;
        this.ip = HostUtil.IP;
        this.username = username;
        this.password = password;
        this.secret = secret;
        this.authUrl = authUrl;
        this.authRetryTime = authRetryTime;
        this.authHeartRetryTime = authHeartRetryTime;
    }

    /**
     * 通道创建初始化时 发送信息 准备握手验证
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        try {
            Map<String, String> data = new HashMap<String, String>(2);
            data.put("node", ip + ":" + port);
            data.put("secret", AESUtil.aesEncrypt(username + password, secret));
            ChannelInfo channelInfoR = new ChannelInfo("auth", data);
            log.info("服务初始化认证：{}", channelInfoR.toString());

            ctx.writeAndFlush(channelInfoR);
        } catch (Exception e) {
            log.info("auth to server fail!");
        }
    }

    /**
     * 当服务器发送认证信息后,开始启动心跳发送
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ChannelInfo channelInfo) throws Exception {
        try {

            if (Objects.equals(channelInfo.getType(),"auth")) {
                //认证成功
                if (SUCCESS.equals(channelInfo.getContent())) {
                    log.info("认证成功：{}", channelInfo.getContent());

                    //认证成功先取消定时（有可能是之前持续认证任务）
                    cancelScheduledFuture();
                    //开启定时发送心跳任务
                    this.scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(new HeartTask(ctx, ip, port), 3, authHeartRetryTime, TimeUnit.SECONDS);
                } else {
                    log.info("服务认证失败：{}", channelInfo.getContent());
                    //开启重新认证
                    this.scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(new AgainRegisterTask(ctx, ip, port, username, password, secret), 3, authRetryTime, TimeUnit.SECONDS);
                }
            } else if (Objects.equals(channelInfo.getType(), "heart")) {
                log.info("client is active: {}", channelInfo.getContent());
            }
        } catch (Exception e) {
            ctx.close();
            log.info("heart to server {} fail!");
        }
    }

    /**
     * 若是出现异常 取消定时
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (cause instanceof IOException) {
            log.error("客户端 {} 和远程断开连接!", ctx.channel().localAddress());
        } else {
            log.error(cause.getMessage(), cause);
        }
        cancelScheduledFuture();
        ctx.channel().close();
        ctx.close();
        reconnection(ctx);
    }

    /**
     * 重新建立连接
     *
     * @param ctx
     */
    private void reconnection(ChannelHandlerContext ctx) {
        log.info("重新建立连接!");
        new StartClient(port, username, password, secret, authUrl, authRetryTime, authHeartRetryTime);
    }

    private void cancelScheduledFuture() {
        if (this.scheduledFuture != null) {
            this.scheduledFuture.cancel(true);
            this.scheduledFuture = null;
        }
    }
}
